
/**
 * Java的数组既可以存储基本类型的数据，也可以存储引用类型的数据，只要所有的数组元素具有相同的类型即可。
 * 值得指出的是，数组也是一种数据类型，它本身是一种引用类型。
 * 例如int是一个基本类型，但int[]（这是定义数组的一种方式）就是一种引用类型了。
 * 编译：javac -encoding UTF-8 ArrayTest.java
 * 运行：java ArrayTest
 */
public class ArrayTest {
    /**
     * 数组的定义
     * Java语言支持两种语法格式来定义数组：
     * type[] arrayName;
     * type arrayName[];
     * 推荐使用第一种
     * <p>
     * 数组是一种引用类型的变量，因此使用它定义一个变量时，仅仅表示定义了一个引用变量（也就是定义了一个指针），
     * 这个引用变量还未指向任何有效的内存，因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量，
     * 并未指向任何有效的内存空间，所以还没有内存空间来存储数组元素，因此这个数组也不能使用，只有对数组进行初始化后才可以使用。
     * <p>
     * 注意：定义数组时不能指定数组的长度。
     */
    public static void ArrayDefineTest() {
        int[] arr = null;
        System.out.println(arr);
    }

    /**
     * 数组的初始化
     * Java语言中数组必须先初始化，然后才可以使用。
     * 所谓初始化，就是为数组的数组元素分配内存空间，并为每个数组元素赋初始值。
     * <p>
     * 数组的初始化有如下两种方式。
     * ➢ 静态初始化：初始化时由程序员显式指定每个数组元素的初始值，由系统决定数组长度。
     * ➢ 动态初始化：初始化时程序员只指定数组长度，由系统为数组元素分配初始值。
     */
    public static void ArrayInitTest() {
        // 静态初始化
        int[] intArr;
        intArr = new int[]{5, 6, 8, 20};

        // 初始化数组时数组元素的类型是定义数组时指定的数组元素的子类
        Object[] objArr = new String[]{"Java", "数组"};
        // 静态初始化
        Object[] objArr2 = new Object[]{"Java", "数组"};

        // 除此之外，静态初始化还有如下简化的语法格式：
        int[] a = {5, 6, 7, 9};

        // 动态初始化
        // 动态初始化只指定数组的长度，由系统为每个数组元素指定初始值。
        int[] prices = new int[5];
        Object[] books = new String[4];

        /*
         * 执行动态初始化时，只需指定数组的长度,系统将负责为这些数组元素分配初始值。
         * ➢ 数组元素的类型是基本类型中的整数类型（byte、short、int和long），则数组元素的值是0。
         * ➢ 数组元素的类型是基本类型中的浮点类型（float、double），则数组元素的值是0.0。
         * ➢ 数组元素的类型是基本类型中的字符类型（char），则数组元素的值是'\u0000'。
         * ➢ 数组元素的类型是基本类型中的布尔类型（boolean），则数组元素的值是false。
         * ➢ 数组元素的类型是引用类型（类、接口和数组），则数组元素的值是null。
         *
         * 注意：不要同时使用静态初始化和动态初始化，
         * 也就是说，不要在进行数组初始化时，既指定数组的长度，也为每个数组元素分配初始值。
         */

        // 使用数组
        for (int i = 0; i < prices.length; i++) {
            System.out.println(prices[i]);
        }
        // 执行上面代码将输出5个0，因为prices数组执行的是默认初始化，数组元素是int类型，系统为int类型的数组元素赋值为0。

        books[0] = "Java";
        books[1] = "数组";
        for (int i = 0; i < books.length; i++) {
            System.out.println(books[i]);
        }
    }

    /**
     * 数组的foreach遍历
     */
    public static void ForEachTest() {
        String[] books = {"Java", "数组", "ForEach"};
        for (String book : books) {
            System.out.println(book);
        }

        /*
         * 使用foreach循环迭代数组元素时，并不能改变数组元素的值，
         * 因此不要对foreach的循环变量进行赋值。
         */
        for (String book : books) {
            book = "foreach";
            System.out.println(book);
        }
        System.out.println("修改后：");
        for (String book : books) {
            System.out.println(book);
        }
    }

    /**
     * 内存中的数组
     * 数组引用变量只是一个引用，这个引用变量可以指向任何有效的内存，只有当该引用指向有效内存后，才可通过该数组变量来访问数组元素。
     * 与所有引用变量相同的是，引用变量是访问真实对象的根本方式。也就是说，如果希望在程序中访问数组对象本身，则只能通过这个数组的引用变量来访问它。
     * 实际的数组对象被存储在堆（heap）内存中；如果引用该数组对象的数组引用变量是一个局部变量，那么它被存储在栈（stack）内存中。
     * <p>
     * 如果需要访问堆内存中的数组元素，则程序中只能通过p[index]的形式实现。也就是说，数组引用变量是访问堆内存中数组元素的根本方式。
     */
    public static void ArrayInRam() {
        int[] a = {5, 7, 20};
        int[] b = new int[4];
        System.out.println("b数组的长度为：" + b.length);
        for (int i = 0, length = a.length; i < length; i++) {
            System.out.println(a[i]);
        }
        for (int i = 0, length = b.length; i < length; i++) {
            System.out.println(b[i]);
        }
        // 因为a是int[]类型，b也是int[]类型，所以可以将a的值赋给b
        // 也就是让b引用指向a引用指向的数组
        b = a;
        System.out.println("b数组的长度为：" + b.length);
    }

    public static void PrimitiveArrayTest() {
        int[] iArr; // 在栈内存中定义了一个空引用（就是iArr数组变量），这个引用并未指向任何有效的内存，当然无法指定数组的长度。
        iArr = new int[5]; // 动态初始化后，系统将负责为该数组分配内存空间，并分配默认的初始值：所有数组元素都被赋为值0，
        for (int i = 0; i < iArr.length; i++) {
            iArr[i] = i + 10; // 当循环为该数组的每个数组元素依次赋值后，此时每个数组元素的值都变成程序显式指定的值。
        }
    }

    public static void ReferenceArrayTest() {
        // 先定义一个类
        class Person {
            public int age;
            public double height;

            public void info() {
                System.out.println("我的年龄是：" + age + ", 我的身高是：" + height);
            }
        }

        Person[] students;      // 这行代码仅仅在栈内存中定义了一个引用变量，也就是一个指针，这个指针并未指向任何有效的内存区。
        students = new Person[2];   // 对students数组执行动态初始化，动态初始化由系统为数组元素分配默认的初始值：null，即每个数组元素的值都是null。
        Person zhang = new Person();
        zhang.age = 15;
        zhang.height = 158;
        Person lee = new Person();
        lee.age = 16;
        lee.height = 161;
        students[0] = zhang;
        students[1] = lee;
        // 下面两行代码的结果完全一样，因为lee和students[1]指向的是同一个Person实例
        lee.info();
        students[1].info();
    }

    /**
     * Java语言里提供了支持多维数组的语法。但是如果从数组底层的运行机制上来看，没有多维数组。
     * Java的所有类型（不包括数组类型），则出现了定义二维数组的语法：
     * type[][] arrName;
     * Java语言采用上面的语法格式来定义二维数组，但它的实质还是一维数组，只是其数组元素也是引用，数组元素里保存的引用指向一维数组。
     */
    public static void TwoDimensionTest() {
        int[][] a;  // 定义一个二维数组
        // 把a当成一维数组进行初始化，初始化a是一个长度为4的数组
        // a数组的数组元素又是引用类型
        a = new int[4][];
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);   // 都是null
        }
        // 初始化a数组的第一个元素
        a[0] = new int[2];
        a[0][1] = 6;
        for (int i = 0; i < a[0].length; i++) {
            System.out.println(a[0][i]);
        }

        // 定义一个b数组变量，这个数组变量指向一个长度为3的数组，
        // 这个数组的每个数组元素又是一个数组类型，它们各指向对应的长度为4的int[]数组，每个数组元素的值为0。
        // 同时初始化二维数组的两个维数
        int[][] b = new int[3][4];

        // 还可以使用静态初始化方式来初始化二维数组。
        // 使用静态初始化方式来初始化二维数组时，二维数组的每个数组元素都是一维数组，因此必须指定多个一维数组作为二维数组的初始化值。
        // 使用静态初始化语法来初始化一个二维数组
        String[][] str1 = new String[][]{new String[3], new String[]{"Hello"}};
        // 使用简化的静态初始化语法来初始化二维数组
        String[][] str2 = {new String[3], new String[]{"Hello"}};

        /*
         * 结论：二维数组是一维数组，其数组元素是一维数组；
         * 三维数组也是一维数组，其数组元素是二维数组……从这个角度来看，
         * Java语言里没有多维数组。
         */
    }

    public static void main(String[] args) {
        ArrayDefineTest();
        ArrayInitTest();
        ForEachTest();
        ArrayInRam();
        PrimitiveArrayTest();
        ReferenceArrayTest();
        TwoDimensionTest();
    }
}
